﻿namespace Microsoft.Services.TestTools.UITesting.Html
{
    using System;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UITesting;

    /// <summary>
    /// Page Object base class
    /// </summary>
    /// <remarks>
    /// Use this base class to simplify the implementation of the Page Object pattern.
    /// </remarks>
    public abstract class Page
    {

        #region launcher

        /// <summary>
        /// Launches the specified page.
        /// </summary>
        /// <typeparam name="T">The Page to launch</typeparam>
        /// <returns>The launched page</returns>
        /// <example>
        /// Launch the home page.
        /// <code>
        /// <![CDATA[
        /// var home = Page.Launch<Home>();
        /// ]]>
        /// </code>
        /// </example>
        /// <seealso cref="EntryUriAttribute"/>
        /// <seealso cref="BaseUriAttribute"/>
        /// <seealso cref="ClearCookiesAttribute"/>
        /// <seealso cref="ClearCacheAttribute"/>
        public static T Launch<T>() where T : Page, new()
        {
            return Launch<T>(
                ShouldClearCookies<T>(),
                ShouldClearCache<T>());
        }


        public static T LaunchWindow<T>() where T : Page, new()
        {
            var page = new T();

            string application = null;
            string applicationPath = null;

            if (HasAttribute<T, ApplicationAttribute>())
            {
                application = GetAttribute<T, ApplicationAttribute>().Application;
            }

            LoadConfiguration(page);
            applicationPath = page.TestConfiguration.GetApplicationPath(application);

            ApplicationUnderTest window = ApplicationUnderTest.Launch(applicationPath);
            page.Window = window;
            return page;
        }

        public static T LaunchWindow<T>(string applicationPath) where T : Page, new()
        {
            var page = new T();

            ApplicationUnderTest window = ApplicationUnderTest.Launch(applicationPath);
            page.Window = window;
            return page;
        }


        /// <summary>
        /// Launches the specified page.
        /// </summary>
        /// <typeparam name="T">The Page to launch</typeparam>
        /// <param name="clearCookies">if set to <c>true</c> [clear cookies].</param>
        /// <param name="clearCache">if set to <c>true</c> [clear cache].</param>
        /// <returns>
        /// The launched page
        /// </returns>
        /// <example>
        ///  Launch the home page and override any 
        /// <see cref="ClearCacheAttribute"/> and <see cref="ClearCookiesAttribute"/>
        /// <code>
        /// <![CDATA[
        /// var home = Page.Launch<Home>(false,true);
        /// ]]>
        /// </code>
        /// </example>
        /// <seealso cref="EntryUriAttribute"/>
        /// <seealso cref="BaseUriAttribute"/>
        /// <seealso cref="ClearCookiesAttribute"/>
        /// <seealso cref="ClearCacheAttribute"/>
        /// <remarks>
        /// Ignores the <see cref="ClearCookiesAttribute"/> and <see cref="ClearCacheAttribute"/>
        /// </remarks>
        public static T Launch<T>(bool clearCookies, bool clearCache) where T : Page, new()
        {
            if (clearCookies)
            {
                BrowserWindow.ClearCookies();
            }

            if (clearCache)
            {
                BrowserWindow.ClearCache();
            }

            var page = new T();
            LoadConfiguration(page);
            Uri launchUri = GetLaunchUri(page);
            var browser = BrowserWindow.Launch(launchUri);
            page.Browser = browser;
            return page;
        }

        public static T Launch<T>(string url) where T : Page, new()
        {

            var page = new T();
            Uri launchUri = new Uri(url);
            var browser = BrowserWindow.Launch(launchUri);
            page.Browser = browser;
            return page;
        }


        /// <summary>
        /// Checks if the current page is decorated with the ClearCacheAttribute.
        /// </summary>
        /// <typeparam name="T">The type of the page</typeparam>
        /// <returns>True if the browser cache should be cleared</returns>
        private static bool ShouldClearCache<T>()
        {
            return HasAttribute<T, ClearCookiesAttribute>();
        }

        /// <summary>
        /// Checks if the current page is decorated with the ClearCookiesAttribute.
        /// </summary>
        /// <typeparam name="T">The type of the page</typeparam>
        /// <returns>True if the browser cookies should be cleared</returns>
        private static bool ShouldClearCookies<T>()
        {
            return HasAttribute<T, ClearCookiesAttribute>();
        }

        /// <summary>
        /// Determines whether the page has the specified attribute.
        /// </summary>
        /// <typeparam name="T">The page type</typeparam>
        /// <typeparam name="A">The attribute type</typeparam>
        /// <returns>
        ///   <c>true</c> if this instance has attribute; otherwise, <c>false</c>.
        /// </returns>
        private static bool HasAttribute<T, A>()
        {
            return typeof(T).GetCustomAttributes(false).Any(x => x.GetType() == typeof(A));
        }

        /// <summary>
        /// Gets the attribute.
        /// </summary>
        /// <typeparam name="T">The page type</typeparam>
        /// <typeparam name="A">The attribute type</typeparam>
        /// <returns>The attribute</returns>
        private static A GetAttribute<T, A>()
        {
            return (A)typeof(T).GetCustomAttributes(false).SingleOrDefault(x => x.GetType() == typeof(A));
        }

        /// <summary>
        /// Gets the launch Uri for the specified page.
        /// </summary>
        /// <typeparam name="T">The page type</typeparam>
        /// <param name="page">The page.</param>
        /// <returns>The launch Uri</returns>
        private static Uri GetLaunchUri<T>(T page) where T : Page
        {
            string baseUriName = null;
            if (HasAttribute<T, BaseUriAttribute>())
            {
                baseUriName = GetAttribute<T, BaseUriAttribute>().BaseUriName;
            }

            Uri baseUri = page.TestConfiguration.GetBaseUri(baseUriName);

            if (HasAttribute<T, EntryUriAttribute>())
            {
                Uri relativeUri = GetAttribute<T, EntryUriAttribute>().RelativeUri;
                return new Uri(baseUri, relativeUri);
            }

            return baseUri;
        }

        /// <summary>
        /// MEF composition container. 
        /// </summary>
        [SuppressMessage(
            "StyleCop.CSharp.OrderingRules",
            "SA1201:ElementsMustAppearInTheCorrectOrder",
            Justification = "Reviewed. Suppression is OK here.")]
        private static CompositionContainer _container;

        /// <summary>
        /// Initializes the composition container and loads the configuration
        /// </summary>
        /// <param name="page">
        /// The page.
        /// </param>
        /// <typeparam name="T">The page type</typeparam>
        private static void LoadConfiguration<T>(T page)
        {
            if (_container == null)
            {
                _container = new CompositionContainer(new AssemblyCatalog(typeof(T).Assembly));
            }

            _container.ComposeParts(page);
        }

        #endregion

        #region instance

        /// <summary>
        /// Gets or sets the test configuration.
        /// </summary>
        /// <value>
        /// The test configuration.
        /// </value>
        /// <seealso cref="IWebUITestConfiguration"/>
        [SuppressMessage(
            "StyleCop.CSharp.OrderingRules",
            "SA1201:ElementsMustAppearInTheCorrectOrder",
            Justification = "Reviewed. Suppression is OK here. Logical grouping"),
         Import(typeof(IWebUITestConfiguration))]
        protected IWebUITestConfiguration TestConfiguration { get; set; }

        /// <summary>
        /// Returns a new page instance and forwards the browser instance to the new page.
        /// </summary>
        /// <typeparam name="T">The <see cref="Page"/> that the parent <see cref="Page"/> navigated to</typeparam>
        /// <returns>The page that was navigated to</returns>
        /// <example>
        /// Clicking on the Home button should havigate back to home.
        /// Instead of launching Home, use NavigatedTo.
        /// <code>
        /// <![CDATA[
        /// Browser.ClickOnButton("Home");
        /// return NavigatedTo<Home>();
        /// ]]>
        /// </code>
        /// </example>
        [SuppressMessage(
            "StyleCop.CSharp.OrderingRules",
            "SA1202:ElementsMustBeOrderedByAccess",
            Justification = "Reviewed. Suppression is OK here. Logical grouping")]
        protected T NavigatedTo<T>() where T : Page, new()
        {
            return new T() { Browser = Browser };
        }

        /// <summary>
        /// Gets or sets the browser.
        /// </summary>
        /// <value>
        /// The browser.
        /// </value>
        [SuppressMessage(
            "StyleCop.CSharp.OrderingRules",
            "SA1201:ElementsMustAppearInTheCorrectOrder",
            Justification = "Reviewed. Suppression is OK here. Logical grouping")]
        protected BrowserWindow Browser { get; set; }

        protected ApplicationUnderTest Window { get; set; }

        /// <summary>
        /// Refreshes this instance.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.OrderingRules",
            "SA1202:ElementsMustBeOrderedByAccess",
            Justification = "Reviewed. Suppression is OK here.")]
        public void Refresh()
        {
            Browser.Refresh();
        }

        /// <summary>
        /// Closes this instance.
        /// </summary>
        [SuppressMessage(
            "StyleCop.CSharp.OrderingRules",
            "SA1202:ElementsMustBeOrderedByAccess",
            Justification = "Reviewed. Suppression is OK here.")]
        public void Close()
        {
            Browser.Close();
        }

        #endregion
    }
}